/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using DaveSexton.Controls;
using System.Xml;
using DaveSexton.DocProject.Sandcastle.TopicManagement;
using DaveSexton.DocProject.Sandcastle.Configuration;

namespace DaveSexton.DocProject.Sandcastle.ContentManagement
{
	public sealed class XmlDocumentationDisplayManager
	{
		#region Public Properties
		public const string XmlDocumentationFormatTagDisplayPrefix = "doc";
		public const string XmlDocumentationFormatTagDisplayPrefixWithSeparator = XmlDocumentationFormatTagDisplayPrefix + ":";

		public static ICollection<string> XmlDocumentationFormatTags
		{
			get
			{
				return formatTags.AsReadOnly();
			}
		}

		public static string TopicDesignerTemplateHtml
		{
			get
			{
				return Resources.HtmlTemplates.TopicDesigner;
			}
		}

		public string CssDirectory
		{
			get
			{
				return cssDirectory;
			}
		}

		public IEnumerable<Uri> StyleSheets
		{
			get
			{
				if (string.IsNullOrEmpty(cssDirectory))
					yield break;

				string topicDesignerOverrides = null;

				foreach (string file in System.IO.Directory.GetFiles(cssDirectory, "*.css", 
					System.IO.SearchOption.AllDirectories))
				{
					if (Configuration.Presentation.IsTopicDesignerFile(file))
						topicDesignerOverrides = file;
					else
						yield return new Uri(file);
				}

				if (topicDesignerOverrides != null)
					yield return new Uri(topicDesignerOverrides);
			}
		}
		#endregion

		#region Private / Protected
		private static readonly List<string> formatTags = InitializeFormatTags();

		private readonly string cssDirectory;
		private readonly XmlWriterSettings settings;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlDocumentationDisplayManager" /> class.
		/// </summary>
		public XmlDocumentationDisplayManager()
		{
			settings = new XmlWriterSettings();
			settings.Indent = true;
			settings.ConformanceLevel = ConformanceLevel.Fragment;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="XmlDocumentationDisplayManager" /> class.
		/// </summary>
		public XmlDocumentationDisplayManager(string cssDirectory)
			: this()
		{
			if (string.IsNullOrEmpty(cssDirectory))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "cssDirectory");

			this.cssDirectory = cssDirectory;
		}
		#endregion

		#region Methods
		private static List<string> InitializeFormatTags()
		{
			List<string> tags = new List<string>(SandcastleBuildEngineProvider.Configuration.DocumentationTags.FormatTags);
			tags.Sort(StringComparer.OrdinalIgnoreCase);

			return tags;
		}

		private HtmlToXmlConverter CreateConverter(StringBuilder builder, XmlNamespaceManager namespaces)
		{
			HtmlToXmlConverter converter = (namespaces == null)
				? new HtmlToXmlConverter(new System.IO.StringWriter(builder, System.Globalization.CultureInfo.InvariantCulture), settings)
				: new HtmlToXmlConverter(new System.IO.StringWriter(builder, System.Globalization.CultureInfo.InvariantCulture), settings, namespaces);

			converter.RemoveTagPrefixes = false;
			converter.Namespaces.AddNamespace(XmlDocumentationFormatTagDisplayPrefix, "urn:doc:sandcastle");

			return converter;
		}

		public string ConvertHtmlToXml(string html, bool addXmlDocDisplayPrefix)
		{
			return ConvertHtmlToXml(html, addXmlDocDisplayPrefix, null);
		}

		public string ConvertHtmlToXml(string html, bool addXmlDocDisplayPrefix, XmlNamespaceManager namespaces)
		{
			if (string.IsNullOrEmpty(html))
				return html;

			// An <?xml/> tag (below) is being inserted, either by the web browser or the converter, when a new 
			// element is added that has a namespace prefix, even though the prefix and its namespace have already 
			// been declared in the <html> element (verified by viewing the live source.)  It must be removed.
			// 
			// <?xml:namespace prefix = doc />
			//
			html = Regex.Replace(html, @"\<\?xml\:.*?/\>", string.Empty, 
				RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);

			StringBuilder builder = new StringBuilder(html.Length);

			// NOTE: A converter must be created from scratch otherwise if an error occurs during parsing
			// the underlying writer will become invalid and cannot be used again until it's recreated.
			using (HtmlToXmlConverter converter = CreateConverter(builder, namespaces))
			{
				/*converter.AttributeNameProcessor = delegate(string attribute)
				{
					if (string.IsNullOrEmpty(attribute))
						return attribute;

					int prefixSeparator = attribute.IndexOf(':');

					if (prefixSeparator > 0 && prefixSeparator < attribute.Length - 1
						&& string.Equals("xmlns", attribute.Substring(0, prefixSeparator), StringComparison.Ordinal)
						&& converter.Namespaces.HasNamespace(attribute.Substring(prefixSeparator + 1)))
						// NOTE: This class only supports namespaces that are declared on the root element.  Namespaces in 
						// the content must be removed.
						return null;
					else
						return attribute;
				};*/

				converter.TagNameProcessor = delegate(string tag)
				{
					if (addXmlDocDisplayPrefix)
					{
						if (formatTags.BinarySearch(tag, StringComparer.OrdinalIgnoreCase) > -1)
							return XmlDocumentationFormatTagDisplayPrefixWithSeparator + tag;
					}
					else
					{
						int prefixSeparatorIndex = tag.IndexOf(':');

						if (prefixSeparatorIndex > 0 && prefixSeparatorIndex < tag.Length - 1 &&
							string.Equals(tag.Substring(0, prefixSeparatorIndex + 1), XmlDocumentationFormatTagDisplayPrefixWithSeparator, StringComparison.OrdinalIgnoreCase))
						{
							return tag.Substring(prefixSeparatorIndex + 1);
						}
					}

					return tag;
				};

				converter.Convert(html);

				return builder.ToString();
			}
		}
		#endregion
	}
}
